package org.truenewx.tnxjee.service.impl.spec.fsm;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.truenewx.tnxjee.core.util.BeanUtil;
import org.truenewx.tnxjee.core.util.function.TrFunction;
import org.truenewx.tnxjee.model.entity.unity.Unity;
import org.truenewx.tnxjee.model.spec.user.UserIdentity;
import org.truenewx.tnxjee.service.impl.unity.AbstractUnityService;
import org.truenewx.tnxjee.service.spec.fsm.StateIntransitableException;
import org.truenewx.tnxjee.service.spec.fsm.StateMachine;
import org.truenewx.tnxjee.service.spec.fsm.StateTransitAction;
import org.truenewx.tnxjee.service.spec.fsm.StateTransitContext;
import org.truenewx.tnxjee.service.transaction.annotation.WriteTransactional;

/**
 * 抽象的有限状态机
 *
 * @param <U> 单体类型
 * @param <K> 标识类型
 * @param <S> 状态枚举类型
 * @param <T> 转换枚举类型
 * @param <I> 用户标识类型
 * @author jianglei
 */
public class StateMachineImpl<U extends Unity<K>, K extends Serializable, S extends Enum<S>, T extends Enum<T>, I extends UserIdentity<?>>
        extends AbstractUnityService<U, K> implements StateMachine<U, K, S, T, I> {

    /**
     * 起始状态
     */
    private final S initialState;
    private String statePropertyName = "state";
    private final Map<S, Map<T, TrFunction<U, I, Object, S>>> stateTransitionMapping = new HashMap<>();
    private final Map<T, StateTransitAction<U, S, T, I>> transitionActionMapping = new HashMap<>();

    public StateMachineImpl(S initialState) {
        this.initialState = initialState;
    }

    public void setStatePropertyName(String statePropertyName) {
        if (StringUtils.isNotBlank(statePropertyName)) {
            this.statePropertyName = statePropertyName;
        }
    }

    public void addTransit(S beginState, T event, TrFunction<U, I, Object, S> endStateDecider) {
        this.stateTransitionMapping.computeIfAbsent(beginState, k -> new HashMap<>())
                .put(event, endStateDecider);
    }

    public void addTransit(S beginState, T event, S endState) {
        addTransit(beginState, event, (unity, userIdentity, payload) -> endState);
    }

    public void setActions(Collection<? extends StateTransitAction<U, S, T, I>> actions) {
        for (StateTransitAction<U, S, T, I> action : actions) {
            this.transitionActionMapping.put(action.getTransition(), action);
        }
    }

    @Override
    public S getInitialState() {
        return this.initialState;
    }

    @Override
    public Set<T> getTransitions(S state) {
        return this.stateTransitionMapping.getOrDefault(state, new HashMap<>()).keySet();
    }

    @Override
    public Map<S, Object> getEndStates(S beginState, T transition) {
        // 需子类覆写实现才可得具体条件描述
        throw new UnsupportedOperationException();
    }

    @Override
    @WriteTransactional
    public U transit(K id, T transition, I userIdentity, Object payload) {
        U unity = load(id);
        S beginState = getState(unity);
        S endState = getEndState(unity, transition, userIdentity, payload);
        if (endState == null) {
            throw new StateIntransitableException(beginState, transition);
        }

        StateTransitContext<S, I> context = null;
        StateTransitAction<U, S, T, I> action = this.transitionActionMapping.get(transition);
        if (action != null) {
            context = new StateTransitContext<>(beginState, userIdentity, payload);
            action.prepare(unity, context);
        }
        save(unity, transition, endState, userIdentity);
        if (action != null) {
            action.done(unity, context);
        }
        return unity;
    }

    protected S getEndState(U unity, T transition, I userIdentity, Object payload) {
        S beginState = getState(unity);
        TrFunction<U, I, Object, S> function = this.stateTransitionMapping.computeIfAbsent(beginState,
                k -> new HashMap<>()).get(transition);
        if (function != null) {
            return function.apply(unity, userIdentity, payload);
        }
        return null;
    }

    protected S getState(U unity) {
        return BeanUtil.getPropertyValue(unity, this.statePropertyName);
    }

    protected void save(U unity, T transition, S endState, I userIdentity) {
        BeanUtil.setPropertyValue(unity, this.statePropertyName, endState);
        save(unity);
    }

}
